/**********************************************************************************
// SpaceRace
// 
// Creation:	06 May 2011
// Updated:		23 Ago 2011
// Compiler:	Visual Studio 2011
//
// Notes:		Space Race is an asteroids inspired game in which
//              the player controls a space ship and must race the clock 
//              from one point to another destroing space waste and enemies 
//				along the way
//
**********************************************************************************/

#include "SpaceRace.h"
#include <fstream>

/**********************************************************************************/

SpaceRace::SpaceRace()
{
	backg                = nullptr;		// background image
	debugbox             = nullptr;		// debug box
	keymapbar            = nullptr;		// keymap bar
	helpbar				 = nullptr;     // help bar
	levelbar             = nullptr;     // level bar

	spaceShip            = nullptr;		// space ship
	missileExplosion     = nullptr;		// missile explosion sprite sheet
	missileExplosionAnim = nullptr;		// missile explosion animation sequence

	gameTitle            = nullptr;		// game title 

	viewAchievements     = false;		// dont show the achievements
	viewBoundingBoxes    = false;		// dont show the bounding boxes
	viewPlayerTrace      = false;		// dont show player trace
	
	// player trace is empty
	ZeroMemory(&playerTrace, sizeof(playerTrace));
	dotsNum = 0;						
}


/**********************************************************************************/

void SpaceRace::GameInit()
{	
	// -----------------------------
	// read game records from file
	// -----------------------------

	// open file
	std::ifstream fin;
	fin.open("records.dat", std::ios_base::in | std::ios_base::binary);

	// if the openning fails
	if (!fin.is_open())
	{
		// records file does not exist
		// set records to default values
		records[0].time = 6.0f;
		records[0].dots = 0;
		records[0].path = nullptr;

		records[1].time = 10.0f;
		records[1].dots = 0;
		records[1].path = nullptr;

		overallRecord = 16.0f;
	}
	else
	{
		// succesful opening 
		// read records from file
		overallRecord = 0;
		for (int i=0; i <= NUMLEVELS; ++i)
		{
			fin.read((char*) &records[i], sizeof(records[i]));
			records[i].path = new Trace[records[i].dots];
			fin.read((char*) records[i].path, records[i].dots * sizeof(Trace));
			overallRecord += records[i].time;
		}

	}
	fin.close();

	// -----------------------------

	// initialize input and keyboard 
	input.Initialize();
	input.InitializeKeyboard();

	// initialize and load sound effects into memory
	sound.Initialize();	

	sound.Load("Resources\\JetWash.wav");
	sound.Frequency(UP,44100);
	
	sound.Load("Resources\\JetWash.wav");
	sound.Frequency(DOWN,22050);
	sound.Volume(DOWN, 9500);

	sound.Load("Resources\\Pulse.wav");
	sound.Frequency(PULSE, 11025);
	sound.Volume(PULSE, 9000);

	sound.Load("Resources\\Explosion.wav");
	sound.Frequency(EXPLOSION,11025);
	sound.Volume(EXPLOSION, 9000);

	sound.Load("Resources\\Explosion.wav");
	sound.Frequency(MISSILEHIT,11025);
	sound.Volume(MISSILEHIT, 8800);

	sound.Load("Resources\\Blow.wav");
	sound.Frequency(BLOW,22050);
	sound.Volume(BLOW, 8000);

	sound.Load("Resources\\Beep.wav");
	sound.Frequency(BEEP,11025);
	sound.Volume(BEEP, 15000);

	sound.Load("Resources\\Intro.wav");
	sound.Frequency(INTRO, 11025);
	sound.Volume(INTRO, 10000);
	sound.Play(INTRO,1);

	sound.Load("Resources\\Transition.wav");
	sound.Frequency(TRANSITION,11025);
	sound.Volume(TRANSITION, 9200);

	sound.Load("Resources\\LevelUp.wav");
	sound.Frequency(LEVELUP, 11025);
	sound.Volume(LEVELUP, 9000);

	sound.Load("Resources\\Unlocked.wav");
	sound.Frequency(UNLOCKED, 11025);
	sound.Volume(UNLOCKED, 10000);

	// Viewport
	SetGameWorldSize(1920, 1536);

	playerVpBox.left   = viewport.left   = 0.0f + (GetGameWorldWidth() - GetWindowWidth())/2;
	playerVpBox.top    = viewport.top    = 0.0f + (GetGameWorldHeight() - GetWindowHeight())/2;
	playerVpBox.right  = viewport.right  = viewport.left + (GetWindowWidth()-1);
	playerVpBox.bottom = viewport.bottom = viewport.top + (GetWindowHeight()-1);

	// the borders are positioned around the playable area
	// they ensure every missile will collide with something
	// and than no need to test "out of world" for missiles
	borders[0] = Rect(0, 0, GetGameWorldWidth()-1.0f, 100);
	borders[0].MoveTo(0, 0);
	borders[0].customType = objBorder;
	borders[0].color = Color(128,128,128);
	scene.RegisterStatic(borders[0]);

	borders[1] = Rect(0, 0, GetGameWorldWidth()-1.0f, 100);
	borders[1].MoveTo(0.0f, GetGameWorldHeight()-101.0f);
	borders[1].customType = objBorder;
	borders[1].color = Color(128,128,128);
	scene.RegisterStatic(borders[1]);

	borders[2] = Rect(0, 0, 100, GetGameWorldHeight()-1.0f);
	borders[2].MoveTo(0, 0);
	borders[2].customType = objBorder;
	borders[2].color = Color(128,128,128);
	scene.RegisterStatic(borders[2]);

	borders[3] = Rect(0, 0, 100, GetGameWorldHeight()-1.0f);
	borders[3].MoveTo(GetGameWorldWidth()-101.0f, 0.0f);
	borders[3].customType = objBorder;
	borders[3].color = Color(128,128,128);
	scene.RegisterStatic(borders[3]);

	// initial game state
	achievementsPage = 0;
	prevState = HOME;
	gameState = HOME;
	gameLevel = 0;
	
	// -------------------------------------------------------------------------
	// load resources into video memory
	
	backg            = new Background("Resources/SpaceArt.jpg");
	missileExplosion = new Sprite("Resources/MissileExplosion.png", 64, 64, 5);
	debugbox         = new Sprite("Resources/debugBox.png");
	keymapbar        = new Sprite("Resources/keymapBar.png");
	helpbar          = new Sprite("Resources/helpBar.png");
	levelbar         = new Sprite("Resources/levelBar.png");
	
	finish[GREEN]    = new Sprite("Resources/FinishGreen.png");
	finish[YELLOW]   = new Sprite("Resources/FinishYellow.png");
	finish[RED]      = new Sprite("Resources/FinishRed.png");

	traceDot[GREEN]  = new Sprite("Resources/DotGreen.png");
	traceDot[YELLOW] = new Sprite("Resources/DotYellow.png");
	traceDot[RED]    = new Sprite("Resources/DotRed.png");
	traceDot[WHITE]  = new Sprite("Resources/DotWhite.png");
	traceDot[ORANGE] = new Sprite("Resources/DotOrange.png");
	
	spaceShip        = new Ship();
	gameTitle        = new GameTitle(GetMidGameWorldX(), GetMidGameWorldY());

	titleFont        = new Font("Berlin Sans FB", 28);
	bodyFont         = new Font("Berlin Sans FB", 19);
	smallFont        = new Font("Berlin Sans FB", 16);

	achievements     = new Achievements();
	achievBar        = new Sprite("Resources/AchievementsBar.png");
}

/**********************************************************************************/

void SpaceRace::GameShutdown()
{
	delete achievBar;
	delete achievements;

	delete smallFont;
	delete bodyFont;
	delete titleFont;	

	delete gameTitle;
	delete spaceShip;

	for (int i=0; i<5; i++)
		delete traceDot[i];

	for (int i=0; i<3; i++)
		delete finish[i];	

	delete levelbar;
	delete helpbar;
	delete keymapbar;
	delete debugbox;
	delete missileExplosion;
	delete backg;	

	for (int i=0; i<NUMLEVELS; i++)
		if (records[i].path)
			delete [] records[i].path;
}


/**********************************************************************************/

void SpaceRace::GameLogic()
{
	// update keyboard state
	input.GetKeyboardState();

	// game state selection
	switch (gameState)
	{
	// ---------------------------------------------------
	// Home Logic
	// ---------------------------------------------------
	case HOME:

		gameTitle->Logic();

		// quit by pressing ESC
		if (input.KeyPressRelease(DIK_ESCAPE))
			QuitGame();

		// start game by pressing ENTER 
		if (input.KeyPressRelease(DIK_RETURN))
		{
			sound.Play(TRANSITION,0);
			sound.Play(INTRO,1);			
			prevState = gameState;
			gameState = PLEVEL;
			gameLevel = 1;
			Level1Init();
		}
			
		break;

	// ---------------------------------------------------
	// Pre-Level Logic
	// ---------------------------------------------------
	case PLEVEL:
		// if player press any action key 
		// begin the level and start timer
		if (input.KeyPressed(DIK_UP)    ||
			input.KeyPressed(DIK_DOWN)  ||
			input.KeyPressed(DIK_LEFT)  || 
			input.KeyPressed(DIK_RIGHT))
		{
			prevState = gameState;
			gameState = LEVEL;
			timer.Start();
		}

		// view bounding boxes
		if (input.KeyPressRelease(DIK_B))
			viewBoundingBoxes = !viewBoundingBoxes;

		// View Achievements
		if (input.KeyPressRelease(DIK_A))
			viewAchievements = !viewAchievements;

		// return to home screen
		if (input.KeyPressRelease(DIK_ESCAPE))
		{
			viewport.left = 0.0f + (GetGameWorldWidth() - GetWindowWidth())/2;
			viewport.top = 0.0f + (GetGameWorldHeight() - GetWindowHeight())/2;
			viewport.right = viewport.left + (GetWindowWidth()-1);
			viewport.bottom = viewport.top + (GetWindowHeight()-1);

			switch(gameLevel)
			{
			case 1: Level1CleanUp(); break;
			case 2: Level2CleanUp(); break;
			//case 3: Level3CleanUp(); break;
			//case 4: Level4CleanUp(); break;
			}

			gameState = HOME;
			gameLevel = 0;
			sound.Play(TRANSITION,0);
			sound.Play(INTRO,1);
		}
		break;

	// ---------------------------------------------------
	// Level Logic
	// ---------------------------------------------------
	case LEVEL: 	
		GameUpdate();

		// if leaving the current state
		if (gameState != LEVEL)
		{
			// reset keys press-release state 
			// for upcoming game level/state
			input.KeyReset();

			// Stop sounds
			if (sound.Playing(UP))
				sound.Stop(UP);
			if (sound.Playing(DOWN))
				sound.Stop(DOWN);
		}
		break;

	// ---------------------------------------------------
	// Level Complete Logic
	// ---------------------------------------------------
	case LEVELCOMPLETE:

		// save player record only when leaving LEVELCOMPLETE 
		// otherwise the previous time will not be draw corretly
		if ((input.KeyPressed(DIK_ESCAPE) ||  input.KeyPressed(DIK_R) || input.KeyPressed(DIK_N)) && 
			playerTime < records[gameLevel-1].time)
		{
			records[gameLevel-1].time = playerTime;
			records[gameLevel-1].dots = dotsNum;
				
			if (records[gameLevel-1].path)
				delete [] records[gameLevel-1].path;
				
			records[gameLevel-1].path = new Trace[dotsNum];
			for (uint i=0; i<dotsNum; i++)
				records[gameLevel-1].path[i] = playerTrace[i];
		}

		// inspect result
		if (!inspectResult && input.KeyPressRelease(DIK_I))
		{
			// achievement: inspector
			achievements->inspector++;
			if (achievements->inspector == 5)
			{
				achievements->Unlock(INSPECTOR);
				sound.Play(UNLOCKED,0);
			}
			inspectResult = true;
		}
		
		// read arrow keys and adjust viewport for inspect result
		if (inspectResult)
		{
			// Rotate Left
			if (input.KeyPressed(DIK_LEFT))
			{
				viewport.left  -= 10;
				viewport.right -= 10;
			}

			// Rotate Right
			if (input.KeyPressed(DIK_RIGHT))
			{
				viewport.left  += 10;
				viewport.right += 10;
			}

			// Rotate Left
			if (input.KeyPressed(DIK_UP))
			{
				viewport.top    -= 10;
				viewport.bottom -= 10;
			}

			// Rotate Right
			if (input.KeyPressed(DIK_DOWN))
			{
				viewport.top    += 10;
				viewport.bottom += 10;
			}

			if (int(viewport.left) < 0)
			{
				viewport.left  = 0.0f;
				viewport.right = GetWindowWidth()-1.0f;
			}
		
			if (int(viewport.right) > GetGameWorldWidth()-1.0f)
			{
				viewport.right = GetGameWorldWidth()-1.0f;
				viewport.left  = float(GetGameWorldWidth() - GetWindowWidth());
			}
	
			if (int(viewport.top) < 0)
			{
				viewport.top  = 0.0f;
				viewport.bottom = GetWindowHeight()-1.0f;
			}
		
			if (int(viewport.bottom) > GetGameWorldHeight()-1.0f)
			{
				viewport.bottom = GetGameWorldHeight()-1.0f;
				viewport.top    = float(GetGameWorldHeight() - GetWindowHeight());
			}
		
		} // end inspect result

		// return to home screen
		if (input.KeyPressRelease(DIK_ESCAPE))
		{
			viewport.left = 0.0f + (GetGameWorldWidth() - GetWindowWidth())/2;
			viewport.top = 0.0f + (GetGameWorldHeight() - GetWindowHeight())/2;
			viewport.right = viewport.left + (GetWindowWidth()-1);
			viewport.bottom = viewport.top + (GetWindowHeight()-1);

			switch(gameLevel)
			{
			case 1: Level1CleanUp(); break;
			case 2: Level2CleanUp(); break;
			//case 3: Level3CleanUp(); break;
			//case 4: Level4CleanUp(); break;
			}

			gameState = HOME;
			gameLevel = 0;
			sound.Play(TRANSITION,0);
			sound.Play(INTRO,1);
		}

		// view Bounding Boxes
		if (input.KeyPressRelease(DIK_B))
			viewBoundingBoxes = !viewBoundingBoxes;

		// view Achievements
		if (input.KeyPressRelease(DIK_A))
			viewAchievements = !viewAchievements;

		// restart Level
		if (input.KeyPressed(DIK_R))
		{
			switch(gameLevel)
			{
			case 1: Level1CleanUp(); Level1Init(); break;
			case 2: Level2CleanUp(); Level2Init(); break;
			//case 3: Level3CleanUp(); Level3Init(); break;
			//case 4: Level4CleanUp(); Level4Init(); break;
			}

			gameState = PLEVEL;
		}

		// go to next level
		if (input.KeyPressed(DIK_N))
		{
			switch(gameLevel)
			{
			case 1: Level1CleanUp(); break;
			case 2: Level2CleanUp(); break;
			//case 3: Level3CleanUp(); break;
			//case 4: Level4CleanUp(); break;
			}
			
			gameState = PLEVEL;
			gameLevel++;
			
			switch(gameLevel)
			{
			case 1: Level1Init(); break;
			case 2: Level2Init(); break;
			//case 3: Level3Init(); break;
			//case 4: Level4Init(); break;
			}
		}

		break;

	// ---------------------------------------------------
	// Game Over Logic
	// ---------------------------------------------------
	case GAMEOVER:

		// inspect result
		if (!inspectResult && input.KeyPressRelease(DIK_I))
		{
			// achievement: Inspector
			achievements->inspector++;
			if (achievements->inspector == 5)
			{
				achievements->Unlock(INSPECTOR);
				sound.Play(UNLOCKED,0);
			}
			inspectResult = true;
		}

		// read arrow keys and adjust viewport for inspect result
		if (inspectResult)
		{
			// Rotate Left
			if (input.KeyPressed(DIK_LEFT))
			{
				viewport.left  -= 10;
				viewport.right -= 10;
			}

			// Rotate Right
			if (input.KeyPressed(DIK_RIGHT))
			{
				viewport.left  += 10;
				viewport.right += 10;
			}

			// Rotate Left
			if (input.KeyPressed(DIK_UP))
			{
				viewport.top    -= 10;
				viewport.bottom -= 10;
			}

			// Rotate Right
			if (input.KeyPressed(DIK_DOWN))
			{
				viewport.top    += 10;
				viewport.bottom += 10;
			}

			if (int(viewport.left) < 0)
			{
				viewport.left  = 0.0f;
				viewport.right = GetWindowWidth()-1.0f;
			}
		
			if (int(viewport.right) > GetGameWorldWidth()-1.0f)
			{
				viewport.right = GetGameWorldWidth()-1.0f;
				viewport.left  = float(GetGameWorldWidth() - GetWindowWidth());
			}
	
			if (int(viewport.top) < 0)
			{
				viewport.top  = 0.0f;
				viewport.bottom = GetWindowHeight()-1.0f;
			}
		
			if (int(viewport.bottom) > GetGameWorldHeight()-1.0f)
			{
				viewport.bottom = GetGameWorldHeight()-1.0f;
				viewport.top    = float(GetGameWorldHeight() - GetWindowHeight());
			}
		
		} // end inspect result

		// ---------------------------------------------------

		// return to home screen
		if (input.KeyPressRelease(DIK_ESCAPE))
		{
			viewport.left = 0.0f + (GetGameWorldWidth() - GetWindowWidth())/2;
			viewport.top = 0.0f + (GetGameWorldHeight() - GetWindowHeight())/2;
			viewport.right = viewport.left + (GetWindowWidth()-1);
			viewport.bottom = viewport.top + (GetWindowHeight()-1);

			switch(gameLevel)
			{
			case 1: Level1CleanUp(); break;
			case 2: Level2CleanUp(); break;
			//case 3: Level3CleanUp(); break;
			//case 4: Level4CleanUp(); break;
			}

			gameState = HOME;
			gameLevel = 0;
			sound.Play(TRANSITION,0);
			sound.Play(INTRO,1);
		}

		// view bounding boxes
		if (input.KeyPressRelease(DIK_B))
			viewBoundingBoxes = !viewBoundingBoxes;

		// view achievements
		if (input.KeyPressRelease(DIK_A))
			viewAchievements = !viewAchievements;

		// restart level
		if (input.KeyPressed(DIK_R))
		{
			switch(gameLevel)
			{
			case 1: Level1CleanUp(); Level1Init(); break;
			case 2: Level2CleanUp(); Level2Init(); break;
			//case 3: Level3CleanUp(); Level3Init(); break;
			//case 4: Level4CleanUp(); Level4Init(); break;
			}

			gameState = PLEVEL;
		}
		break;

	} // end switch: game state selection		
} 

/**********************************************************************************/

void SpaceRace::GameUpdate()
{
	// ---------------------------------------------------
	// Timer check
	// ---------------------------------------------------
	elapsedTime = timer.Elapsed();

	if (elapsedTime >= timeLimit)
	{
		elapsedTime = timeLimit;
		sound.Play(BEEP,0);
		gameState = GAMEOVER;
	}

	// ---------------------------------------------------
	// Player trace path 
	// ---------------------------------------------------
	playerTrace[dotsNum].x = spaceShip->x;
	playerTrace[dotsNum].y = spaceShip->y;
	dotsNum++;

	// ---------------------------------------------------
	// Set the finish color 
	// ---------------------------------------------------
	if (elapsedTime <= records[gameLevel-1].time)
	{
		finishColorIndex = GREEN;
		recordColorIndex = WHITE;
		finishColor = Color(0,255,0);	// green color
	}
	else if (elapsedTime >= 0.80 * timeLimit)		
	{
		finishColorIndex = RED;
		recordColorIndex = GREEN;
		finishColor = Color(185,0,0);	// red color
	}
	else
	{
		finishColorIndex = YELLOW;
		recordColorIndex = GREEN;
		finishColor = Color(255,200,0); // yellow color		
	}
		

	// ---------------------------------------------------
	// Input Logic
	// ---------------------------------------------------

	// Rotate Left
	if (input.KeyPressed(DIK_LEFT))
		spaceShip->Rotate(-4.0f);
	
	// Rotate Right
	if (input.KeyPressed(DIK_RIGHT))
		spaceShip->Rotate(4.0f);

	// ---------------------------------------------------

	// Stop sounds
	if (input.KeyReleased(DIK_UP))
	{
		// achievements: Pedal To The Metal
		achievements->pedal = false;
		if (sound.Playing(UP))
			sound.Stop(UP);
	}
	if (input.KeyReleased(DIK_DOWN))
		if (sound.Playing(DOWN))
			sound.Stop(DOWN);

	// Stop animations
	spaceShip->Animate(INERTIA);

	// ---------------------------------------------------

	// View Bounding Boxes
	if (input.KeyPressRelease(DIK_B))
		viewBoundingBoxes = !viewBoundingBoxes;

	// View Achievements
		if (input.KeyPressRelease(DIK_A))
			viewAchievements = !viewAchievements;

	// view player trace
	if (input.KeyPressRelease(DIK_T))
	{
		viewPlayerTrace = !viewPlayerTrace;

		if (viewPlayerTrace)
		{
			// achievement: Tracer
			achievements->tracer++;
			if (achievements->tracer == 1)
			{
				achievements->Unlock(TRACER);
				sound.Play(UNLOCKED,0);
			}
		}
	}

	// Translate (move up)
	if (input.KeyPressed(DIK_UP))    
	{
		spaceShip->Accelerate();
		spaceShip->Animate(FORWARD);
	}
	if (input.KeyPressRelease(DIK_UP))
	{
		sound.Play(UP,0);
		spaceShip->animation->Restart();
	}
	
	// Translate (move down)
	if (input.KeyPressed(DIK_DOWN))
	{
		spaceShip->Brake();
		spaceShip->Animate(BACKWARD);
	}
	if (input.KeyPressRelease(DIK_DOWN))
	{
		sound.Play(DOWN,0);
		spaceShip->animation->Restart();
	}

	// Space Bar (Fire Missile)
	if (input.KeyPressRelease(DIK_SPACE))
	{
		sound.Play(PULSE,0);
		Missile * mis = new Missile(spaceShip);
		missiles.push_back(mis);
		scene.RegisterMoving(*mis);
	}

	// viewing achievements list
	if (viewAchievements)
	{
		if (input.KeyPressRelease(DIK_PGUP))
			achievementsPage--;
		if (input.KeyPressRelease(DIK_PGUP))
			achievementsPage++;
		if (achievementsPage < 0)
			achievementsPage = 0;
		if (achievementsPage > NUMACHIEV/5 - 1)
			achievementsPage = NUMACHIEV/5 - 1;
	}

	// ---------------------------------------------------
	// Update Objects
	// ---------------------------------------------------

	// move space ship
	spaceShip->Translate(spaceShip->speed.GetX(), -1.0f * spaceShip->speed.GetY());

	// move space waste
	for (imov = moving.begin(); imov != moving.end(); ++imov)
		(*imov)->Rotate(2);

	// move player viewport
	playerVpBox.left   += spaceShip->speed.GetX();
	playerVpBox.right  += spaceShip->speed.GetX();
	playerVpBox.top    -= spaceShip->speed.GetY();
	playerVpBox.bottom -= spaceShip->speed.GetY();

	// achievement: Redundant
	if (gameLevel == 1 && !achievements->GetUnlocked(LEFTRIGHT))
	{
		int oldShipX = int(playerTrace[dotsNum-1].x);
		int midProlixY = GetMidGameWorldY()+100;
		int midProlixX = GetMidGameWorldX();

		// redundant right
		if (achievements->redundantR[0] || (spaceShip->IntY() > midProlixY && spaceShip->IntX() > midProlixX))
		{
			achievements->redundantR[0] = true;

			if (achievements->redundantR[1] || 
				(oldShipX >= midProlixX && spaceShip->IntX() < midProlixX && spaceShip->IntY() > midProlixY) )
			{
				achievements->redundantR[1] = true;

				if (achievements->redundantR[2] || 
					(spaceShip->IntY() < midProlixY && spaceShip->IntX() < midProlixX))
				{
					achievements->redundantR[2] = true;

					if (achievements->redundantR[3] || 
						(oldShipX <= midProlixX && spaceShip->IntX() > midProlixX && spaceShip->IntY() < midProlixY) )
					{
						achievements->redundantR[3] = true;

						if (achievements->redundantR[4] || 
							(spaceShip->IntY() > midProlixY && spaceShip->IntX() > midProlixX))
						{
							achievements->redundantR[4] = true;
						}
					}
				}
			}
		}

		// redundant left
		if (achievements->redundantL[0] || (spaceShip->IntY() > midProlixY && spaceShip->IntX() < midProlixX))
		{
			achievements->redundantL[0] = true;

			if (achievements->redundantL[1] || 
				(oldShipX <= midProlixX && spaceShip->IntX() > midProlixX && spaceShip->IntY() > midProlixY))
			{
				achievements->redundantL[1] = true;

				if (achievements->redundantL[2] || 
					(spaceShip->IntY() < midProlixY && spaceShip->IntX() > midProlixX))
				{
					achievements->redundantL[2] = true;

					if (achievements->redundantL[3] || 
						(oldShipX >= midProlixX && spaceShip->IntX() < midProlixX && spaceShip->IntY() < midProlixY))
					{
						achievements->redundantL[3] = true;

						if (achievements->redundantL[4] || 
							(spaceShip->IntY() > midProlixY && spaceShip->IntX() < midProlixX))
						{
							achievements->redundantL[4] = true;
						}
					}
				}
			}
		}
	}

	// move all the missiles
	for (imis = missiles.begin(); imis != missiles.end(); ++imis)
		(*imis)->Move();

	// ---------------------------------------------------
	// Update Viewport
	// ---------------------------------------------------
	viewport.left   += spaceShip->speed.GetX();
	viewport.right  += spaceShip->speed.GetX();
	viewport.top    -= spaceShip->speed.GetY();
	viewport.bottom -= spaceShip->speed.GetY();

	float vpIncrement = 5.0f;														// amount to add each frame
	float deltaHoriz = playerVpBox.left - viewport.left;							// calculate the horizontal gap 
	float deltaVert  = playerVpBox.top - viewport.top;								// calculate the vertical gap
	float horizRatio = abs(deltaHoriz)/abs(deltaVert);								// compute horizontal versus vertical ratio
	float horizInc   = (deltaHoriz > 0 ? vpIncrement : -vpIncrement) * horizRatio;	// horizontal increment
	float vertInc    = (deltaVert  > 0 ? vpIncrement : -vpIncrement);				// vertical ratio is 1.0 
		
	if (abs(deltaHoriz) > abs(horizInc))
	{
		viewport.left   += horizInc;
		viewport.right  += horizInc;
	}
	else
	{
		viewport.left   = playerVpBox.left;
		viewport.right  = playerVpBox.right;
	}
			
	if (viewport.left < 0)
	{
		viewport.left  = 0.0f;
		viewport.right = GetWindowWidth()-1.0f;
	}
	else if (viewport.right > GetGameWorldWidth()-1)
	{
		viewport.right = GetGameWorldWidth()-1.0f;
		viewport.left  = float(GetGameWorldWidth() - GetWindowWidth());
	}
	
	// ---------------------------------------------------
		
	if (abs(deltaVert) > abs(vertInc))
	{
		viewport.top     += vertInc;
		viewport.bottom  += vertInc;
	}
	else
	{
		viewport.top     = playerVpBox.top;
		viewport.bottom  = playerVpBox.bottom;
	}
	
	if (viewport.top < 0)
	{
		viewport.top  = 0.0f;
		viewport.bottom = GetWindowHeight()-1.0f;
	}
	else if (viewport.bottom >  GetGameWorldHeight()-1)
	{
		viewport.bottom = GetGameWorldHeight()-1.0f;
		viewport.top    = float(GetGameWorldHeight() - GetWindowHeight());
	}

	// ---------------------------------------------------
	// Update Space Ship Animation
	// ---------------------------------------------------

	if (spaceShip->animation->Active())
		spaceShip->animation->NextFrame();

	// ---------------------------------------------------
	// Collision Detection
	// ---------------------------------------------------

	// test for collision
	if (scene.CollisionDetection())
	{
		// pair of objects in collision
		pair<GameObject*,GameObject*> pairObjs;

		// while the pair returned is not empty, process collision
		while ((pairObjs = scene.OnCollisionObjects()) != pair<GameObject*,GameObject*>(nullptr,nullptr))
		{
			// take first and second object in collision
			GameObject * first  = (GameObject *) pairObjs.first;
			GameObject * second = (GameObject *) pairObjs.second;

			// if rock is the first object, invert pointers
			// so there is no need to consider multiple cases
			if (first->customType == objWaste)
			{
				first  = (GameObject *) pairObjs.second;
				second = (GameObject *) pairObjs.first;
			}

			switch (first->customType)
			{
			// -------
			// Missile
			// -------
			case objMissile:

				// destroy space waste
				if (second->customType != objFinish)
				{
					// set the volume of the explosion sound proportional 
					// to the distance between the missile and the player
					Point a(first->x, first->y);
					Point b(spaceShip->x, spaceShip->y);
					float distance = a.Distance(b);
					sound.Volume(MISSILEHIT, 9600 - int(distance));
					sound.Play(MISSILEHIT,0);

					if (second->customType == objWaste)
					{
						// start waste object explosion animation
						Sprite * waste = (Sprite*) second;
						sound.Volume(EXPLOSION, 9800 - int(distance));
						sound.Play(EXPLOSION,0);
						scene.UnRegisterMoving(*waste);
						moving.remove(waste);
						exploding.push_back(waste);
						uint wasteSeq[6] = {2,3,4,5,6};
						waste->animation = new Animation(wasteSeq, 5, 85, true, 0.0f, 0.0f, ONETIMER);
					}
					else
					{
						// start missile explosion animation
						missileExplosionAnim = new Animation(nullptr, 10, 8, false, first->x, first->y);
						misExpList.push_back(missileExplosionAnim);
					}

					// erase missile 
					scene.UnRegisterMoving(*((Missile *) first));
					missiles.remove((Missile*) first);
					delete (Missile*) first;
				}
				break;	
			// -------
			//  ship 
			// -------
			case objShip:
				// get the second object type 
				switch (second->customType)
				{
				// Finish Marker
				case objFinish:

					// if all space waste has been destroyed
					if (moving.empty())
					{
						// player' ship reached finish mark
						sound.Play(LEVELUP,0);
						gameState = LEVELCOMPLETE;
						playerTime = elapsedTime;
										

						// achievement: Pedal To The Metal
						if (gameLevel == 1 && !achievements->GetUnlocked(PEDAL))
						{
							if ((playerTime >= 0.8 * timeLimit) && achievements->pedal)
							{
								achievements->Unlock(PEDAL);
								sound.Play(UNLOCKED,0);
							} else
								achievements->pedal = true;
						}

						// achievement: Lef-Right Redundant
						if (gameLevel == 1 && !achievements->GetUnlocked(LEFTRIGHT))
						{
							if (achievements->redundantR[0] && achievements->redundantR[1] &&
								achievements->redundantR[2] && achievements->redundantR[3] &&
								achievements->redundantR[4])
							{
								achievements->redundantRight = true;

								// achievement: Redundant
								if (!achievements->GetUnlocked(REDUNDANT))
								{								
									achievements->Unlock(REDUNDANT);
									sound.Play(UNLOCKED,0);
								}
							}

							if (achievements->redundantL[0] && achievements->redundantL[1] && 
								achievements->redundantL[2] && achievements->redundantL[3] && 
								achievements->redundantL[4])
							{
								achievements->redundantLeft = true;
								// achievement: Rdundant
								if (!achievements->GetUnlocked(REDUNDANT))
								{								
									achievements->Unlock(REDUNDANT);
									sound.Play(UNLOCKED,0);
								}
							}

							if (achievements->redundantRight && achievements->redundantLeft)
							{
								achievements->Unlock(LEFTRIGHT);
								sound.Play(UNLOCKED,0);
							}						
						}	
					}

					break;
				
				// Borders or Objects
				case objBorder:
				case objFixed:
				case objWaste:
					// play collision sound
					sound.Play(BLOW,0);
					// set ship explosion animation
					spaceShip->Explode();	
					gameState = GAMEOVER;
					break;
				}
				break;

			} // end switch		
		
		} // end while

	} // end test for collision
}

/**********************************************************************************/

void SpaceRace::GameDraw()
{
	// --------------------------------------------
	// Elements drawn for all game states
	// --------------------------------------------

	// draw background using current viewport
	RECT source = {long(viewport.left), long(viewport.top), long(viewport.right), long(viewport.bottom)};
	backg->Draw(source);

	// --------------------------------------------
	// Elements drawn for state HOME
	// --------------------------------------------
	if (gameState == HOME)
	{
		gameTitle->Draw();
	}
	// --------------------------------------------
	// Elements drawn for states: 
	// - PLEVEL
	// - LEVEL
	// - LEVELCOMPLETE 
	// - GAMEOVER
	// --------------------------------------------
	else
	{
		// draw player trace
		if (viewPlayerTrace)
		{
			for (uint i=0; i < dotsNum; i++)
			{
				traceDot[ORANGE]->x = playerTrace[i].x;
				traceDot[ORANGE]->y = playerTrace[i].y;
				traceDot[ORANGE]->Draw();
			}
		}

		// draw sprites or bounding boxes
		if (viewBoundingBoxes)
		{
			LockScreen();
			// draw screen borders)
			borders[0].Draw();
			borders[1].Draw();
			borders[2].Draw();
			borders[3].Draw();

			// draw bounging boxes of fixed game objects
			for (ifix = fixed.begin(); ifix != fixed.end(); ++ifix)
				((Poly *) (*ifix)->boundingbox)->Draw();
			
			// draw bounging boxes of moving game objects
			for (imov = moving.begin(); imov != moving.end(); ++imov)
				((Poly *) (*imov)->boundingbox)->Draw();

			// draw bounding boxes of missiles
			for (imis = missiles.begin(); imis != missiles.end(); ++imis)
				((Point *) (*imis)->boundingbox)->Draw();

			// draw space ship bounding box
			if (gameState != LEVELCOMPLETE)
				if (elapsedTime >= timeLimit || gameState != GAMEOVER)
					((Poly *) spaceShip->boundingbox)->Draw();

			// draw finish marker
			finishBB.color = finishColor;
			finishBB.Draw();

			UnLockScreen();
		} 
		else
		{

			// draw fixed game objects
			for (ifix = fixed.begin(); ifix != fixed.end(); ++ifix)
				(*ifix)->Draw();

			// draw moving game objects
			for (imov = moving.begin(); imov != moving.end(); ++imov)
				(*imov)->Draw();
		
			// draw missiles
			for (imis = missiles.begin(); imis != missiles.end(); ++imis)
				(*imis)->Draw();
			
			// draw the ship
			if (gameState != LEVELCOMPLETE)
				if (elapsedTime >= timeLimit || gameState != GAMEOVER)
					spaceShip->Draw();

			finish[finishColorIndex]->Draw();
		}

		// draw explosions of objects
		iexp = exploding.begin();
		while (iexp != exploding.end())
		{
			if ((*iexp)->animation->Active())
			{
				// draw active explosion
				(*iexp)->animation->NextFrame();
				(*iexp)->Draw();
				++iexp;
			}
			else
			{
				// clean inactive explosion
				delete (*iexp)->animation;
				delete (*iexp);
				iexp = exploding.erase(iexp);
			}
		}


		// draw explosions of missiles
		iMisExp = misExpList.begin();
		while (iMisExp != misExpList.end())
		{
			if ((*iMisExp)->Active())
			{
				// draw active explosion
				(*iMisExp)->NextFrame();
				missileExplosion->animation = (*iMisExp);
				missileExplosion->Draw();
				++iMisExp;
			}
			else
			{
				// clean inactive explosion
				delete *iMisExp;
				iMisExp = misExpList.erase(iMisExp);
			}
		}

		// draw the ship explosion animation
		if (spaceShip->isExploding())
			spaceShip->DrawExplosion();
	

		// draw elapsed time in seconds and miliseconds
		if (gameState == PLEVEL)
			sprintf_s(text, "00:%06.3f", timeLimit);
		else
			sprintf_s(text, "00:%06.3f", elapsedTime);
			
		sysFont->Draw(finPosX-100-int(viewport.left), finPosY-8-int(viewport.top), 
					  text, finishColor, 200, 30, DT_CENTER); 

		if (viewBoundingBoxes)
		{
			debugbox->MoveTo(viewport.left + 155, viewport.top + 100);
			debugbox->Draw();
			sprintf_s(text, "Space Race");
			sysFont->Draw(55, 40, text);
			sprintf_s(text, "Space ship location: %d x %d", int(spaceShip->x), int(spaceShip->y));
			sysFont->Draw(55, 80, text);
			sprintf_s(text, "Background size: %d x %d", GetGameWorldWidth(), GetGameWorldHeight());
			sysFont->Draw(55, 100, text);
			sprintf_s(text, "Viewport: (%d,%d) to (%d,%d)", int(viewport.left), int(viewport.top), int(viewport.right), int(viewport.bottom));
			sysFont->Draw(55, 120, text);
		}

	} // end of elements drawn for all game states but HOME

	// --------------------------------------------
	// Draw elements for specific game states
	// --------------------------------------------
	switch (gameState)
	{
	// --------------------------------------------
	// PLEVEL Draw
	// --------------------------------------------
	case PLEVEL:

		// draw level caption
		levelbar->Draw();
		sprintf_s(text, "LEVEL %d", gameLevel);
		titleFont->Draw(0, 100, text, Color(200,200,200), GetWindowWidth(), 30, DT_CENTER);

		sprintf_s(text, "Move the ship");
		bodyFont->Draw(0, 180, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
		sprintf_s(text, "to START");
		bodyFont->Draw(0, 205, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);

		// draw finish caption
		sprintf_s(text, "FINISH");
		smallFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top-35), text, Color(0,255,0), 200, 30, DT_CENTER);
		//smallFont->Draw(0, int(finPosY- viewport.top - 35), text, Color(0,255,0), GetWindowWidth(), 30, DT_CENTER);

		// draw help instructions
		helpbar->Draw();
		keymapbar->Draw();
		switch (gameLevel)
		{
		case 1:	
			sprintf_s(text, "Reach the finish marker within");
			bodyFont->Draw(0, GetWindowHeight()-105, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
			sprintf_s(text, "the time limit to complete the level");
			bodyFont->Draw(0, GetWindowHeight()-85, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
			break;
		case 2:
			sprintf_s(text, "Destroy space waste and reach");
			bodyFont->Draw(0, 330, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
			sprintf_s(text, "finish marker within the time limit");
			bodyFont->Draw(0, 350, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
			break;
		}
		// navigation keys
		sprintf_s(text, "(Arrows) Move the Space Ship");
		smallFont->Draw(0, GetWindowHeight()-24, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
		break;

	// --------------------------------------------
	// LEVELCOMPLETE Draw
	// --------------------------------------------
	case LEVELCOMPLETE:

		// draw player traces
		if (inspectResult)
		{
			// draw record path
			for (uint i=0; i < records[gameLevel-1].dots; i++)
			{	
				traceDot[recordColorIndex]->x = records[gameLevel-1].path[i].x;
				traceDot[recordColorIndex]->y = records[gameLevel-1].path[i].y;
				traceDot[recordColorIndex]->Draw();
			}

			// draw player path
			for (uint i=0; i < dotsNum; i++)
			{
				traceDot[finishColorIndex]->x = playerTrace[i].x;
				traceDot[finishColorIndex]->y = playerTrace[i].y;
				traceDot[finishColorIndex]->Draw();
			}				
		}

		// level complete text
		sprintf_s(text, "LEVEL COMPLETE");
		smallFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top-65), text, Color(0,255,0), 200, 30, DT_CENTER);
	
		// if player set a new record
		if (playerTime < records[gameLevel-1].time)
		{
			sprintf_s(text, "New Record");
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top-32), text, Color(0,255,0), 200, 30, DT_CENTER);

			sprintf_s(text, "Previous Record");
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top+28), text, Color(190,190,190), 200, 30, DT_CENTER);

			sprintf_s(text, "00:%06.3f", records[gameLevel-1].time);
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top+48), text, Color(190,190,190), 200, 30, DT_CENTER);
		}
		else
		{
			sprintf_s(text, "Your Time");
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top-32), text, finishColor, 200, 30, DT_CENTER);

			sprintf_s(text, "Record");
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top+28), text, Color(0,255,0), 200, 30, DT_CENTER);

			sprintf_s(text, "00:%06.3f", records[gameLevel-1].time);
			sysFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top+48), text, Color(0,255,0), 200, 30, DT_CENTER);
		}

		// achievements summary
		achievBar->MoveTo(viewport.right + 20.0f, viewport.bottom - 140.0f);
		achievBar->Draw();

		sprintf_s(text, "%d of 100 Achievement Points", achievements->Total());
		sysFont->Draw(GetWindowWidth() - 205, GetWindowHeight() - 160, text, Color(180,180,180));
		sprintf_s(text, "(A) for more information");
		smallFont->Draw(GetWindowWidth() - 205, GetWindowHeight() - 140, text, Color(180,180,180));

		// navigation keys
		keymapbar->MoveTo(viewport.left + GetWindowWidth()/2.0f, viewport.bottom - 15.0f);
		keymapbar->Draw();
		
		if (inspectResult)
			sprintf_s(text, "(N) Next Level    (Arrows) Move Camera    (R) Restart Level");
		else
			sprintf_s(text, "(N) Next Level    (I) Inspect Trace    (R) Restart Level");

		smallFont->Draw(0, GetWindowHeight()-24, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
		break;

	// --------------------------------------------
	// GAMEOVER Draw
	// --------------------------------------------
	case GAMEOVER:

		// draw player trace
		if (inspectResult && !viewPlayerTrace)
		{
			for (uint i=0; i < dotsNum; i++)
			{
				traceDot[ORANGE]->x = playerTrace[i].x;
				traceDot[ORANGE]->y = playerTrace[i].y;
				traceDot[ORANGE]->Draw();
			}
		}
		
		// navigation keys
		keymapbar->MoveTo(viewport.left + GetWindowWidth()/2.0f, viewport.bottom - 15.0f);
		keymapbar->Draw();

		// level complete text
		sprintf_s(text, "GAME OVER");
		smallFont->Draw(int(finPosX-viewport.left-100), int(finPosY-viewport.top-35), text, Color(0,255,0), 200, 30, DT_CENTER);

		if (inspectResult)
			sprintf_s(text, "(Arrows) Move Camera    (R) Restart level");
		else
			sprintf_s(text, "(I) Inspect Trace    (R) Restart level");

		smallFont->Draw(0, GetWindowHeight()-24, text, Color(180,180,180), GetWindowWidth(), 30, DT_CENTER);
		break;

	}	// end switch

	// --------------------------------------------
	// Elements drawn for states: 
	// - PLEVEL
	// - LEVEL
	// - LEVELCOMPLETE 
	// - GAMEOVER
	// --------------------------------------------
	if (gameState != HOME)
	{
		// print achievements list
		if (viewAchievements)
		{
			const int achievementsPerPage = 5;
			Achievement * vet = achievements->List();
			Color achievColor;

			for (int i=achievementsPage*achievementsPerPage; i < achievementsPage*achievementsPerPage+achievementsPerPage; i++)
			{
				achievBar->MoveTo((viewport.left+viewport.right)/2.0f+35.0f, viewport.top + 350.0f + 70*i);
				achievBar->Draw();
				
				achievements->Icons()->MoveTo((viewport.left+viewport.right)/2.0f-245.0f, viewport.top + 350.0f + 70*i);
				if (vet[i].unlocked)
				{
					achievements->Icons()->Draw(i);
					achievColor = Color(180,180,180);
				}
				else
				{
					achievements->Icons()->Draw(i+NUMACHIEV);
					achievColor = Color(80,80,80);
				}

				sprintf_s(text, "%s", vet[i].name);
				bodyFont->Draw(GetMidWindowX()-190, 330 + 70*i, text, achievColor, GetWindowWidth(), 30, DT_LEFT);

				sprintf_s(text, "%s", vet[i].description);
				smallFont->Draw(GetMidWindowX()-190, 350 + 70*i, text, achievColor, GetWindowWidth(), 30, DT_LEFT);

				sprintf_s(text, "%d", vet[i].points);
				bodyFont->Draw(GetMidWindowX()+235, 340 + 70*i, text, achievColor, GetWindowWidth(), 30, DT_LEFT);
			}
		}

		// Draw achievements
		if (achievements->Waiting())
		{
			achievBar->MoveTo(viewport.right + 20.0f, viewport.bottom - 240.0f);
			achievBar->Draw();

			sprintf_s(text, "Achievement Unlocked");
			smallFont->Draw(GetWindowWidth() - 205, GetWindowHeight() - 260, text, Color(180,180,180));
			sprintf_s(text, "%dP", achievements->GetInfo().points);
			sysFont->Draw(GetWindowWidth() - 205, GetWindowHeight() - 240, text, Color(180,180,180));
			sprintf_s(text, "- %s", achievements->GetInfo().name);
			smallFont->Draw(GetWindowWidth() - 180, GetWindowHeight() - 240, text, Color(180,180,180));

			achievements->MoveTo(viewport.right - 260.0f, viewport.bottom - 240.0f);
			achievements->Draw();		
		}
	}
} 


/**********************************************************************************/
/*                                  WinMain                                       */
/**********************************************************************************/

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	SpaceRace spacerace;

	//spacerace.SetWindowMode(WINDOWED);
	//spacerace.SetWindowSize(1024,768);
	spacerace.SetWindowBgColor(10,25,55);
	spacerace.SetWindowTitle(string("Space Race"));
	spacerace.SetWindowIcon(LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)));
	spacerace.ShowWindowCursor(false);
	spacerace.Initialize(hInstance);
	spacerace.Start();
	
	return 0;
}

/**********************************************************************************/